use super::*;

#[derive(Debug, Clone, Copy)]
pub enum ErrorType {
	Normal,
	Type,
	Range,
}

#[derive(Clone, Copy)]
pub struct Error<'val> {
	pub(crate) env: &'val Env,
	pub(crate) inner: sys::napi_value,
}

impl<'val> Error<'val> {
	/// Creates a new Error object with the specified type, code, and message
	pub fn new<'env>(env: &'env Env, ty: ErrorType, code: Value<'env>, msg: Value<'env>) -> Result<'static, Error<'env>> {
		match ty {
			ErrorType::Normal => Error::new_normal(env, code, msg),
			ErrorType::Type => Error::new_type(env, code, msg),
			ErrorType::Range => Error::new_range(env, code, msg),
		}
	}

	/// Creates a new Error object with the specified code and message
	pub fn new_normal<'env>(env: &'env Env, code: Value<'env>, msg: Value<'env>) -> Result<'static, Error<'env>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_create_error(env.raw(), code.raw(), msg.raw(), &mut ret) } //
			.result("napi_create_error")?;
		Ok(Error { env, inner: ret })
	}

	/// Creates a new TypeError object with the specified code and message
	pub fn new_type<'env>(env: &'env Env, code: Value<'env>, msg: Value<'env>) -> Result<'static, Error<'env>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_create_type_error(env.raw(), code.raw(), msg.raw(), &mut ret) } //
			.result("napi_create_error")?;
		Ok(Error { env, inner: ret })
	}

	/// Creates a new RangeError object with the specified code and message
	pub fn new_range<'env>(env: &'env Env, code: Value<'env>, msg: Value<'env>) -> Result<'static, Error<'env>> {
		let mut ret = core::ptr::null_mut();
		unsafe { sys::napi_create_range_error(env.raw(), code.raw(), msg.raw(), &mut ret) } //
			.result("napi_create_error")?;
		Ok(Error { env, inner: ret })
	}

	/// Converts the Error object to a Value
	pub fn v(&'_ self) -> Value<'val> {
		Value::new(self.env, self.inner)
	}

	/// Gets the error message if the value is an error object
	pub fn message(&'_ self) -> Result<'static, Option<String>> {
		self.v().get_named(c"message")?.to()
	}

	/// Gets the error stack if the value is an error object
	pub fn stack(&'_ self) -> Result<'static, Option<String>> {
		self.v().get_named(c"stack")?.to()
	}
}

impl<'val> FromNapi<'val> for Error<'val> {
	/// Converts from Value to Buffer
	fn from_napi(v: Value<'val>) -> Result<'static, Self> {
		if v.is_error()? {
			Ok(Error {
				env: v.env,
				inner: v.inner,
			})
		} else {
			Err(Status::invalid_arg("value is not error"))
		}
	}
}

impl<'val> IntoNapi for Error<'val> {
	/// Converts Buffer to Value
	fn into_napi<'env>(self, env: &'env Env) -> Result<'static, Value<'env>> {
		if self.env.raw() != env.raw() {
			return Err(Status::invalid_arg("self.env not eq arg env"));
		}
		Ok(Value::new(env, self.inner))
	}
}
